import {
  AfterViewInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ContentChild,
  ElementRef,
  EventEmitter,
  forwardRef,
  Input,
  OnChanges,
  OnDestroy,
  OnInit,
  Output,
  QueryList,
  Renderer2,
  SimpleChanges,
  TemplateRef,
  TrackByFunction,
  ViewChild,
  ViewChildren,
  ViewContainerRef,
  ViewEncapsulation,
} from '@angular/core';
import { AnimationEvent } from '@angular/animations';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { SelectionModel } from '@angular/cdk/collections';
import { Overlay, OverlayConfig } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { CdkScrollable, CdkVirtualScrollViewport, ScrollDispatcher } from '@angular/cdk/scrolling';
import { DOWN_ARROW, ENTER, UP_ARROW } from '@angular/cdk/keycodes';
import { fromEvent, Observable, Subject, Subscription, merge, isObservable } from 'rxjs';
import { debounceTime, filter, takeUntil, tap, throttleTime } from 'rxjs/operators';
import {
  every as _every,
  filter as _filter,
  forEach as _forEach,
  isArray as _isArray,
  isNil as _isNil,
  map as _map,
  some as _some,
} from 'lodash';
import {
  doFnResult,
  flatMap as lvFlatMap,
  I18NService,
  InputBoolean,
  InputNumber,
  LvConfig,
  LvFilterMode,
  LvPositionTB,
  LvSelectSM,
  LvSizeLSD,
  OverlayService,
  result as lvResult,
  SelectRemoveChangeProps,
  showItem,
} from '../core/api';
import { OptionItem, SelectFilterConfig as FilterConfig, SelectNode } from './interface';
import { SelectService } from './select.service';
import { SelectDataSource } from './select-data-source';
import { initSelectOptionsError, setSelectionValueError } from './select-errors';
import { ADJUST_POSITION_MAP, getPosition, POSITION_MAP } from './select-position';
import { SelectTriggerComponent } from './select-trigger.component';
import { SubSelectComponent } from './sub-select.component';

@Component({
  selector: 'lv-select',
  templateUrl: './select.component.html',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef((): typeof SelectComponent => SelectComponent),
      multi: true,
    },
    SelectService,
  ],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  animations: [showItem],
  host: {
    class: 'lv-select',
    '[class.lv-select-size-large]': "lvSize === 'large'",
    '[class.lv-select-size-default]': "lvSize === 'default'",
    '[class.lv-select-size-small]': "lvSize === 'small'",
    '[class.disabled]': 'lvDisabled',
    '[class.lv-select-inline]': 'lvInline',
    '[class.lv-select-open]': 'selectOpen',
    '[class.lv-select-multiple]': 'multiple',
    '[class.lv-select-single]': '!multiple',
    '[class.lv-select-simple]': 'lvSimple',
    '(click)': '_toggleSelect($event)',
    '(focus)': 'lvFocus.emit($event)',
    '(blur)': 'lvBlur.emit($event)',
    '(keydown)': 'onKeyDown($event)',
    tabindex: '-1',
  },
})
export class SelectComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy, OnChanges {
  @Input() lvFilterKey: string; // 过滤的key(对于对象来说)
  // 输入输出属性
  @Input() lvOptions: OptionItem[]; // 源数据
  @Input() lvSize: LvSizeLSD = 'default';
  @Input() lvMode: LvSelectSM = 'single'; // 选择模式，默认单选
  @Input() lvTrackBy: TrackByFunction<any>; // 内容列表trackBy
  @Input() @InputBoolean() lvAsync = false; // 是否为异步数据
  @Input() @InputBoolean() lvInline = false; // 内联展示模式
  @Input() @InputBoolean() lvSimple = false;
  @Input() lvDropdownPosition: LvPositionTB; // 下拉位置，设置后不随overlay配置变化
  @Input() @InputNumber() lvVirtualItemSize; // 每行高度

  @Input() @InputBoolean() lvLoading = false; // 是否加载中
  @Input() @InputNumber() lvMaxSelectedItems; // 最多选中项条数

  @Input() @InputNumber() lvVirtualMaxBuffer; // 渲染新项时最大缓冲高度
  @Input() @InputNumber() lvVirtualMinBuffer; // 视图之外渲染的最小缓冲区数，若缓存区数量低于此数量将渲染新项
  @Output() lvBlur = new EventEmitter<any>(); // 下拉展示区失去焦点时回调事件

  @Input() @InputBoolean() lvDisabled = false; // 是否禁用

  @Output() lvFocus = new EventEmitter<any>(); // 下拉展示区获取焦点时回调事件
  @Input() @InputBoolean() lvShowFilter = false; // 是否展示过滤
  @Input() @InputBoolean() lvFilterFocus = true; // 过滤输入框自动聚焦

  @Input() @InputNumber() lvMaxTagCount: number; // 选中项个数模式下，最多显示多少个tag
  // filter
  @Input() @InputBoolean() lvAsyncFilter = false; // 是否为异步过滤
  @Input() lvValueKey: string | ((o: any) => any); // 自定义value，默认源数据
  @Input() @InputBoolean() lvHideSelected = false; // 隐藏选中项

  @Input() @InputBoolean() lvShowCheckAll = false; // 是否支持全选
  @Input() @InputNumber() lvMaxTagRow: number = 7; // 选中项单行模式下，点了+n之后，超过7（包含收起，可配置）行则出现滚动条
  @Output() lvScrollEnd = new EventEmitter<any>(); // 虚拟滚动到底部时回调事件

  // == virtual scroll
  @Input() @InputBoolean() lvVirtualScroll = false; // 是否使用虚拟滚动（大数据场景使用）
  @Output() lvClearChange = new EventEmitter<any>(); // 清除全部时回调事件
  @Input() lvCompareWith: string | ((o: any) => any); // data被选中的比较方式，默认value地址比较, o 为value

  @Input() lvFilterMode: LvFilterMode = 'startsWith'; // 过滤模式
  @Output() lvRemoveChange = new EventEmitter<any>(); // 删除单个时回调事件
  // node
  @Output() lvOptionChange = new EventEmitter<any>(); // 节点变化回调事件：鼠标移入节点，鼠标移出节点
  // select
  @Output() lvOpenChange = new EventEmitter<boolean>(); // 切换选择下拉回调事件
  @Output() lvFilterChange = new EventEmitter<string>(); // 搜索文本框变化时回调事件
  @Output() lvScrollingChange = new EventEmitter<any>(); // 虚拟滚动中回调事件
  @Input() @InputBoolean() lvFilterCaseSensitive = false; // 是否区分大小写

  @Input() lvScroll: { x?: string | null; y?: string | null }; // 下拉选项水平垂直高度，超过滚动 目前只有y

  @Input() lvContentTemplate: TemplateRef<{ $implicit: any }>; // 自定义内容模板

  @Input() lvPlaceholder: string = this.i18n.get('selectPlaceholder'); // 如果没有选择任何值，显示的占位符
  @ViewChildren('subSelect') subSelect: QueryList<SubSelectComponent>;

  @Input() lvCustomFilter: <T>(data: T[], filter: FilterConfig) => T[]; // 自定义过滤方法
  @ViewChild('panelContent', { static: false }) _panelContent: ElementRef;
  @Input() lvFilterPlaceholder: string = this.i18n.get('searchPlaceholder');
  @Input() lvCustomItemFilter: <T>(data: T, filter: FilterConfig) => boolean; // 自定义单项过滤方法
  @ViewChild('selectPanelHeader') selectPanelHeader: ElementRef<HTMLElement>;
  @ViewChild('selectPanelFooter') selectPanelFooter: ElementRef<HTMLElement>;
  @ViewChild('selectPanelContent') selectPanelContent: ElementRef<HTMLElement>;
  @Input() lvPanelClass: string | string[] | Set<string> | { [key: string]: any }; // 设置下拉面板的class同ngClass语法

  @Input() lvOnAddOption: (inputValue: string) => void | Promise<void> | Observable<void>;
  @ViewChild('selectionPanelTpl', { static: false })
  _selectionPanelTpl: TemplateRef<any>;

  @Input() @InputBoolean() lvShowClear: boolean = LvConfig.selectOptions.lvShowClear || false; // 是否显示清除按钮
  @Input() lvShowAddOption: boolean | { addBtn?: boolean; addAndSelectBtn?: boolean } = false;

  @ViewChild('scrollComponent', { static: false })
  _scrollViewport: CdkVirtualScrollViewport;

  @Input() lvOnAddAndSelectOption: (inputValue: string) => void | Promise<void> | Observable<void>;

  @ContentChild(SelectTriggerComponent, { static: false })
  customTrigger: SelectTriggerComponent;

  @Input() lvPosition: 'topLeft' | 'topCenter' | 'topRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight' =
    'bottomLeft';

  @Input() lvBeforeOpen: () =>
    | (false | void | object)
    | Promise<false | void | object>
    | Observable<boolean | void | object>;

  // 外部可访问变量
  value: any;
  overlayRef;
  adding = false;

  scrollY: string;
  addLoading = false;
  filterValue: string; // 记录搜索框值
  searchLoading: boolean; // 记录searchLoading状态

  virtualScrollY: string;

  animationState = 'void';
  renderOptions: any[] = []; // 实际渲染的下拉选择项
  selectOpen: boolean = false; // 下拉展开状态 NEED_DO: selectionModel替代
  addingInputValue: string = '';
  inputChange$ = new Subject<any>();
  hoveredSubSelect: SubSelectComponent;
  selectedSubSelect: SubSelectComponent;
  lastVirtualScrollHoveredIndex: number | undefined; // 用于虚拟滚动scrollEnd导致的数据重新渲染的情况，只支持一层数据的情况
  addOptionButtons: Array<{ label: string; onClick: () => void }> = [];
  private _direction; // 面板实际展开方向
  private _opened = false; // 记录面板展开动画是否执行完毕
  private _recordModelValue;
  private _goToIndex = false; // 标识是不是初始化通过方法调用触发scroll
  private _activeElement: any;
  private _selectWidth: number;
  private _optionsChangeFlag = false; // 标识lvOptions有变化
  private _childrenKey = 'originData';
  private _autoSelectionChanged = false; // ngModel绑定的选中项发生变化
  // 内部变量
  private _destroy$ = new Subject<void>();
  private _renderSub = Subscription.EMPTY;
  private _refresh$ = new Subject<void>();
  private _maunalSelectionChanged = false; // 用户操作导致选中项发生变化
  private _dataSource: SelectDataSource<any>;
  private _selectionModel: SelectionModel<any>;
  private _openState$ = new Subject<boolean>();
  private _searchSubscribe = Subscription.EMPTY;
  private mergeSubSelectMouseEnter: Subscription;
  private _selectToggle$ = new Subject<boolean>(); // 下拉展开收起流

  constructor(
    public selectService: SelectService,
    private viewContainerRef: ViewContainerRef,
    private overlayService: OverlayService,
    private overlay: Overlay,
    private elementRef: ElementRef,
    private _cdr: ChangeDetectorRef,
    private i18n: I18NService,
    private scrollDispatcher: ScrollDispatcher,
    private renderer: Renderer2,
  ) {
    this._activeElement = elementRef.nativeElement;
  }

  get multiple(): any {
    return this.selectService.isMultipleMode();
  }

  get hasOptions(): boolean {
    return !!this.lvOptions?.length;
  }

  // 半选
  get isHalfCheck(): any {
    const selectedLength = _filter(this.renderOptions, 'selected').length;
    return !this.isAllCheck && !!selectedLength;
  }

  /**
   * 全选
   * 不为disable的项全部选中
   * disabled可不被选中
   * 不能全部为disabled
   */
  get isAllCheck(): any {
    const data = this.renderOptions;
    if (!data.length) {
      return false;
    }

    const disItems = _every(data, 'disabled');
    if (disItems) {
      return false;
    }

    const items = _filter(data, (item): any => item.disabled || item.selected);

    return items.length === data.length;
  }

  _clearSearch(): void {
    this.search('');
  }

  setDisabledState?(): void {}

  setRefresh(): void {
    this._refresh$.next();
  }

  /**
   * 订阅select open/close
   */
  ngAfterViewInit(): void {
    this._subscribeOpenChange();
  }

  registerOnChange(fn: any): void {
    this._onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this._onTouch = fn;
  }

  searchChange(value): void {
    this.inputChange$.next(value);
  }

  getSelectData(): any {
    return this._dataSource.selectData;
  }

  stopPropagation(e: MouseEvent): void {
    e.stopPropagation();
  }

  /**
   * 更新是否展开下拉，没有任何判断条件（外部可调用）
   */
  toggleSelect(open: boolean): void {
    this._selectToggle$.next(open);
  }

  setSearchLoadingStatus(value: boolean): void {
    this.searchLoading = value;
  }

  openAdding(): void {
    this.adding = true;
    this._overlayUpdatePostion();
  }

  initData(): void {
    this._selectionModel = new SelectionModel(this.multiple);
  }

  // 关闭下拉
  clearOverlayRef(): void {
    if (this._existOverlayRef()) {
      this.overlayRef.close();
    }
  }

  compareResult(source: any): any {
    return lvResult(source, this.lvCompareWith);
  }

  result(source: any): any {
    return this.compareResult(lvResult(source, this.lvValueKey));
  }

  _existOverlayRef(): any {
    return this.overlayRef?.overlayRef.hasAttached();
  }

  compareItem(a, b): boolean {
    return this.getValueByKey([a]).pop() === this.getValueByKey([b]).pop();
  }

  // 获取第一个高亮index
  getFirstActiveIndex(): any {
    return this.selectService.getActiveNodeFirstIndex(this.getSelectData());
  }

  getValueByKey(data): any {
    return _map(data, (item): any => lvResult(lvResult(item, this._childrenKey), this.lvValueKey));
  }

  catchError(): void {
    // options 必填 异步直接验证值有问题,使用标识
    if (!this._optionsChangeFlag) {
      throw initSelectOptionsError();
    }
  }

  writeValue(value: any): void {
    this._recordModelValue = value;
    this._autoSelectionChanged = true;
    this._setSelectionModel(value);
  }

  initNodeAcitve(): void {
    const flatData = this.resultToDataMap();
    flatData.forEach((item): void => {
      item.active = false;
    });
  }

  isVirtualScrollRenderedContentTop(baseComponent?: SubSelectComponent, base?: HTMLElement): boolean {
    return baseComponent && !base.offsetTop;
  }

  ngOnDestroy(): void {
    this._destroy$.next();
    this._destroy$.complete();
    this.mergeSubSelectMouseEnter?.unsubscribe();
    this.clearOverlayRef();
  }

  _updateSelectedItems(value?: any): void {
    this._maunalSelectionChanged = true;
    this._recordModelValue = value;
    this._onChange(value);
    this.setRefresh();
  }

  initNodeStatus(): void {
    const flatData = this.resultToDataMap();
    flatData.forEach((item): void => {
      item.active = false;
      item.selected = false;
    });
  }

  /**
   * 点击展示区切换下拉（内部使用）
   */
  _toggleSelect(e: MouseEvent): void {
    e.stopPropagation();
    if (this.lvDisabled) {
      e.preventDefault();
      return;
    }
    this.toggleSelect(!this.selectOpen);
  }

  resultToDataMap(isAllCheck: boolean = false): any {
    const data = isAllCheck ? this.renderOptions : this.getSelectData();
    return lvFlatMap(data, this._childrenKey, true);
  }

  beforeClose(): void {
    if (this._direction === 'up') {
      this.animationState = 'hideUp';
    } else {
      this.animationState = 'hideDown';
    }
    this._cdr.markForCheck();
  }

  _overlayUpdatePostion(): void {
    // 通知 overlay 刷新视图
    setTimeout((): void => {
      this.overlayRef?.overlayRef?.updatePosition();
      window.dispatchEvent(new Event('resize'));
    }, 0);
  }

  /**
   * data --lvValueKey--> valueData --lvCompareWidth-->key
   */
  resultToValueDataMap(): any {
    const flatData = this.resultToDataMap();
    const output = new Map();
    flatData.forEach((item, key): void => {
      output.set(this.result(key), item);
    });
    return output;
  }

  isVirtualScrollTop(
    baseComponent?: SubSelectComponent,
    viewPort?: CdkVirtualScrollViewport,
    base?: HTMLElement,
  ): boolean {
    return !baseComponent || !(viewPort.measureScrollOffset() + base.offsetTop);
  }

  removeBaseHover(base: HTMLElement): void {
    if (!base) {
      return;
    }
    const baseOption = base.querySelector('lv-option');
    this.renderer.removeClass(baseOption, 'hover');
    base.dispatchEvent(new Event('mouseleave'));
  }

  clearSelected(e: MouseEvent): void {
    e.stopPropagation();
    this.initNodeStatus();
    this.value = null;
    this._updateSelectedItems();
    this.selectService.setReFresh();
    this.lvClearChange.emit();
    this.clearOverlayRef();
  }

  _selectValue(value: any, flatData): any {
    const selectedItem = flatData.get(this.compareResult(value));

    if (selectedItem) {
      this._selectionModel.select(selectedItem);
      selectedItem.selected = true;
    }

    return selectedItem;
  }

  /**
   * 订阅body click事件
   * 当下拉为展开状态，点击空白处手动关闭侧拉
   * NEED_DO:点击空白处关闭
   */
  _subscribeBlankClickChange(): void {
    fromEvent(document.body, 'click')
      .pipe(
        filter((): boolean => this.selectOpen),
        takeUntil(this._destroy$),
      )
      .subscribe((): void => {
        this.toggleSelect(false);
      });
  }

  isVirtualScrollRenderedContentBottom(
    baseComponent?: SubSelectComponent,
    viewPort?: CdkVirtualScrollViewport,
    base?: HTMLElement,
  ): boolean {
    return baseComponent && !(viewPort.measureRenderedContentSize() - base.offsetTop - base.clientHeight);
  }

  isVirtualScrollBottom(
    baseComponent?: SubSelectComponent,
    viewPort?: CdkVirtualScrollViewport,
    base?: HTMLElement,
  ): boolean {
    return (
      baseComponent &&
      !(viewPort.getViewportSize() - viewPort.measureScrollOffset() - base.offsetTop - base.clientHeight)
    );
  }

  onAnimationDone(event: AnimationEvent): void {
    let flag1 = event.fromState === 'showUp' && event.toState === 'hideUp';
    let flag2 = event.fromState === 'showDown' && event.toState === 'hideDown';
    if (flag1 || flag2) {
      this._openState$.next(false);
    } else {
      this._openState$.next(true);
    }
  }

  removeSelectedItem(props: SelectRemoveChangeProps): void {
    const { event, node } = props;
    event.stopPropagation();

    if (this.lvDisabled) {
      return;
    }

    node.selected = false;
    this.setNodesActive();

    this.updateSelection(node);
    this.selectService.setReFresh();
    this.lvRemoveChange.emit(node);
  }

  genLastVirtualScrollHoveredIndex(next: HTMLElement): void {
    if (!this.lvVirtualScroll || this.renderOptions.some((item): any => item.children?.length)) {
      return;
    }
    const viewPort = this._scrollViewport;
    this.lastVirtualScrollHoveredIndex = (viewPort.measureScrollOffset() + next.offsetTop) / next.clientHeight;
  }

  ngOnInit(): void {
    this.catchError();
    this.initData();
    this.initAddOptionButtons();

    if (!this.lvVirtualItemSize) {
      this.lvVirtualItemSize = this.selectService.defaultVirtualItemSizeConfig[this.lvSize];
    }

    this._refresh$.pipe(debounceTime(1), takeUntil(this._destroy$)).subscribe((): void => this._cdr.markForCheck());
  }

  toggleCheckAll(): void {
    const checkAll = this.isAllCheck;
    const flatData = this.resultToDataMap(true);
    flatData.forEach((item): void => {
      if (item.disabled) {
        return;
      }
      item.selected = !checkAll;
    });
    this.setNodesActive();

    this.updateSelection();
    this.selectService.setReFresh();
  }

  /**
   * 选择
   */
  moveToNext(next: HTMLElement): void {
    if (!next) {
      return;
    }
    this.genLastVirtualScrollHoveredIndex(next);
    const nextOption = next.querySelector('lv-option');
    next.dispatchEvent(new Event('mouseenter'));
    this.renderer.addClass(nextOption, 'hover');
    if (!this.lvVirtualScroll) {
      next.scrollIntoView?.(false);
    }
  }

  _getPanelConfig(): OverlayConfig {
    return new OverlayConfig({
      minWidth: this._selectWidth,
      hasBackdrop: false,
      scrollStrategy: this.overlay.scrollStrategies.block(),
      positionStrategy: this.overlay
        .position()
        .flexibleConnectedTo(this.elementRef)
        .withPositions([POSITION_MAP[this.lvPosition], ...ADJUST_POSITION_MAP]),
    });
  }

  hoverFirstItem(): void {
    const { baseComponent, base } = this.getBase();
    const next =
      base ||
      (
        this.getNextComponent(baseComponent) ||
        this.excludeDisabledItem((baseComponent?.parent || this).subSelect).first ||
        this.excludeDisabledItem(this.subSelect).first
      )?.elementRef.nativeElement;
    if (next) {
      this.moveToNext(next);
    }
  }

  _subscribeOpenChange(): void {
    this._selectToggle$
      .pipe(
        tap((res): void => {
          this.selectOpen = res;
          if (!res) {
            this.clearOverlayRef();
            this.setRefresh();
          }
        }), // 关闭
        filter((res): any => res), // 打开
        takeUntil(this._destroy$),
      )
      .subscribe((): void => {
        this._openPanelOverlay();
      });
  }

  listenMouseEnter(subSelect: QueryList<SubSelectComponent>, next: HTMLElement): void {
    this.mergeSubSelectMouseEnter?.unsubscribe();
    this.mergeSubSelectMouseEnter = merge(
      ...subSelect.map((item): Observable<any> => fromEvent(item.elementRef.nativeElement, 'mouseenter')),
    ).subscribe((): void => {
      this.removeBaseHover(next);
      this.mergeSubSelectMouseEnter.unsubscribe();
    });
  }

  setNodesActive(): void {
    const flatData = this.resultToDataMap();
    const selectedNodes = [];
    flatData.forEach((item): void => {
      item.active = false;
      if (item.selected) {
        selectedNodes.push(item);
      }
    });

    const activeNodes = (nodes): void => {
      _forEach(nodes, (item): void => {
        item.active = true;
        if (item.parent) {
          activeNodes([item.parent]);
        }
      });
    };

    activeNodes(selectedNodes);
  }

  getNextComponent(baseComponent: SubSelectComponent, previous = false): SubSelectComponent | undefined {
    if (!baseComponent) {
      return undefined;
    }
    const index = Array.from(this.excludeDisabledItem((baseComponent.parent || this).subSelect)).findIndex(
      (item): boolean => item === baseComponent,
    );
    return this.excludeDisabledItem((baseComponent.parent || this).subSelect).get(index + (previous ? -1 : 1));
  }

  /**
   * 订阅virtual scroll 的scroll事件
   * 记录滚动到底部时，触发回调
   * NEED_DO: 追加的数据，显示错位
   */
  scrolledIndexChange(e: any): void {
    if (this._goToIndex) {
      this._goToIndex = false;
      return;
    }
    const view = this._scrollViewport;
    if (!view) {
      return;
    }
    const total = view.getDataLength();
    const size = view.getViewportSize();
    if (!total) {
      return;
    }
    this.lvScrollingChange.emit({ source: view, index: e });
    if (total - Math.floor(size / this.lvVirtualItemSize) === e) {
      this.lvScrollEnd.emit();
    }
  }

  excludeDisabledItem(subSelect: QueryList<SubSelectComponent>): QueryList<SubSelectComponent> {
    const queryList = new QueryList<SubSelectComponent>();
    if (!subSelect) {
      return queryList;
    }
    queryList.reset(
      Array.from(subSelect).filter(
        (item): boolean =>
          !item.elementRef.nativeElement.querySelector('.disabled') &&
          !item.elementRef.nativeElement.querySelector('.lv-option-hover-disabled'),
      ),
    );
    return queryList;
  }

  setSubSelectParent(subSelect: SubSelectComponent[], parent: SubSelectComponent): void {
    if (!subSelect.length) {
      return;
    }
    subSelect.forEach((item): void => {
      item.parent = parent;
      const el = item.elementRef.nativeElement as HTMLElement;
      if (el.querySelector('.selected')) {
        this.selectedSubSelect = item;
      }
      if (el.querySelector('.hover')) {
        this.hoveredSubSelect = item;
      }
      this.setSubSelectParent(Array.from(this.excludeDisabledItem(item.subSelect)), item);
    });
  }

  beforeOpen(resolve): void {
    const openFn = (_resolve): void => {
      _resolve();
    };
    if (!this.lvBeforeOpen) {
      openFn(resolve);
    } else {
      const cb = this.lvBeforeOpen();
      if (cb instanceof Promise) {
        cb.then((res): void => {
          if (res !== false) {
            openFn(resolve);
          }
        });
      } else if (isObservable(cb)) {
        cb.subscribe((res): void => {
          if (res !== false) {
            openFn(resolve);
          }
        });
      } else if (cb !== false) {
        openFn(resolve);
      }
    }
  }

  _subscribeScrollchange(): void {
    this.scrollDispatcher
      .scrolled()
      .pipe(
        filter((): boolean => {
          const view = this._scrollViewport;
          if (!view) {
            return false;
          }
          if (this._goToIndex) {
            this._goToIndex = false;
            return false;
          }
          return view.getRenderedRange().end === view.getDataLength();
        }),
        throttleTime(1000),
      )
      .subscribe((scrollable: CdkScrollable): void => {
        if (scrollable) {
          this.lvScrollEnd.emit();
        }
      });
  }

  search(value?: string): void {
    if (!this.lvAsyncFilter && this._dataSource) {
      this._dataSource.filter = {
        value,
        filterMode: this.lvFilterMode,
        caseSensitive: this.lvFilterCaseSensitive,
        customFilter: this.lvCustomFilter,
        customItemFilter: this.lvCustomItemFilter,
        key: this.lvFilterKey,
      } as FilterConfig;
    }
    this.filterValue = value;
    this.lvFilterChange.emit(value);
    this.setSearchLoadingStatus(true);
    this._scrollViewport?.scrollTo({ top: 0 });
    this._panelContent?.nativeElement?.scrollTo({ top: 0 });
  }

  /**
   * 节点切换选中回调
   * 单选时：只能一个数据被选中
   * 1. NEED_DO：更新active状态，在有子项时
   * 2. 更新选中项
   * 3. 单选时关闭overlay
   */
  itemSeletedChange(source: any): void {
    const { node } = source;
    let changedValue = true;
    if (!this.multiple) {
      this.initNodeStatus(); // 重置
      node.selected = true;
      // 待办:火狐85版本下node和this._dataSource.selectData地址不一致，后续定位，临时解决
      this._dataSource.selectData.forEach((item): void => {
        if (this.compareItem(item, node)) {
          item.selected = true;
        }
      });
      changedValue = !this.compareItem(node, this.value);
    }

    if (changedValue) {
      this.setNodesActive();

      this.updateSelection(node);
    }

    if (!this.multiple) {
      this.toggleSelect(false); // 单选时关闭
    }
  }

  _subscribeRenderChange(dataSource: SelectDataSource<any>): void {
    if (!dataSource) {
      return;
    }

    const dataStream = dataSource.connect();
    this._renderSub.unsubscribe();
    this._renderSub = dataStream.pipe(takeUntil(this._destroy$)).subscribe((res): void => {
      this.renderOptions = [...res];
      this.setSearchLoadingStatus(false);
      this._setSelectionModel(this._recordModelValue); // 解决data更新，选中记录变化问题NEED_DO：状态值独立维护
      this.setRefresh();
      this.virtualScrollY = this.getVirtualScrollY();
      this._cdr.markForCheck();
      setTimeout((): void => {
        this._scrollViewport?.checkViewportSize();
        if (!this.hasChildren()) {
          this.hoverFirstItem();
        }
      });
    });
  }

  /**
   * 展开下拉时，定位到第一个选中的位置
   */
  setPanelHighlightPosition(dom: any): void {
    const contentDom = dom;
    if (!contentDom) {
      return;
    }
    const index = this.getFirstActiveIndex();
    const optionsHeight = contentDom.offsetHeight;
    const itemHeight = this.lvVirtualItemSize;
    const maxHeight = parseInt(this.scrollY, 0);
    if (index < 0 || optionsHeight < maxHeight) {
      return;
    }
    if (this.lvVirtualScroll) {
      const i = Math.floor(maxHeight / this.lvVirtualItemSize / 2 - 1 / 2);
      const toIndex = index - i;
      if (toIndex <= 0) {
        return;
      }
      setTimeout((): void => {
        this._goToIndex = true;
        this._scrollViewport.scrollToIndex(toIndex /* , 'smooth' */);
      }, 0);
      return;
    }
    contentDom.scrollTop = (index + 1) * itemHeight - optionsHeight / 2;
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.lvOptions) {
      this._optionsChangeFlag = true;
      const data = changes.lvOptions.currentValue;
      // 避免重复初始化
      if (!this._dataSource) {
        this._dataSource = new SelectDataSource(data, this.lvAsyncFilter);
        this._subscribeRenderChange(this._dataSource);
      } else {
        this._dataSource.data = data;
      }
    }

    if (changes.lvPanelClass) {
      this.selectService.setPanelClass(changes.lvPanelClass.currentValue);
    }

    const modeChange = changes.lvMode;
    if (modeChange) {
      this.selectService.setMode(modeChange.currentValue);
    }

    if (changes.lvShowFilter) {
      this._searchSubscribe.unsubscribe();

      this._searchSubscribe = this.inputChange$
        .pipe(debounceTime(300), takeUntil(this._destroy$))
        .subscribe((value): void => {
          this.search(value);
        });
    }

    this.scrollY = this.selectService.getScrollY(this.lvScroll, this.lvSize);
  }

  updateSelection(node?: SelectNode): void {
    const data = this.getSelectData();
    let output;
    const value = [];
    const getSelected = (nodes): void => {
      _forEach(nodes, (item): void => {
        if (item.selected) {
          value.push(item);
        }

        getSelected(item.children);
      });
    };
    if (!node || !this.multiple) {
      getSelected(data);
      this.value = value;
    } else {
      this.value = this.value || [];
      const index = this.value.findIndex((item): boolean => this.compareItem(item, node));
      if (index > -1) {
        this.value.splice(index, 1);
      } else {
        this.value.push(node);
      }
      this.value = [...this.value];
    }

    output = this.getValueByKey(this.value);
    if (!this.multiple) {
      this.value = this.value.pop();
      output = output.pop();
    }
    this._updateSelectedItems(output);

    // 和form结合使用的场景可能导致updatePosition时，新Tag还未渲染完成，故标记检测
    this._cdr.markForCheck();

    // 通知 overlay 刷新视图
    this._overlayUpdatePostion();
  }

  afterOpen(overlayInstance, res): void {
    // 设置z-index
    this.renderer.addClass(this.overlayRef.overlayRef.hostElement, 'lv-select-panel-z-index');
    // 定位到第一个选中位置
    this.setPanelHighlightPosition(res.rootNodes[0].querySelector('.lv-select-panel-content'));
    this.overlayRef.overlayRef.hostElement
      .querySelector('.lv-select-panel')
      .style.setProperty('--panel-width', `${this._selectWidth}px`);
    this.selectOpen = true;
    // 获取打开的方向
    overlayInstance.overlayRef._positionStrategy.positionChanges.subscribe((e): void => {
      // 如果面板已经展开，不再改变动画效果
      if (this._opened) {
        return;
      }
      const _position = getPosition(e.connectionPair);
      if (_position === 'topLeft' || _position === 'topCenter' || _position === 'topRight') {
        this.animationState = 'showUp';
        this._direction = 'up';
      } else {
        this.animationState = 'showDown';
        this._direction = 'down';
      }
      this._opened = true;
      this._cdr.markForCheck();
      if (!this.hasChildren()) {
        const openState$ = this._openState$.pipe(filter((item): any => item)).subscribe((): void => {
          this.hoverFirstItem();
          openState$.unsubscribe();
        });
      }
    });
  }

  _setSelectionModel(value: any | any[]): void {
    // 处理选中的值
    // 多选时 传非数组时报错
    if (this.multiple) {
      if (!_isNil(value) && !_isArray(value)) {
        throw setSelectionValueError();
      }
    }

    let _value = value;
    if (!this.multiple) {
      if (_isArray(value)) {
        _value = [value.pop()];
      } else {
        _value = !_isNil(value) && [value];
      }
    }

    // 清除选中态
    // 待办: 是否用selectionModule记录，在更新复杂类型数据时有处理痛点
    this._selectionModel.clear();
    this.initNodeStatus();

    // 设置选中态
    this.value = [];
    const flatData = this.resultToValueDataMap();
    _forEach(_value, (item: any): void => {
      /**
       * 判断是否是异步数据
       * 如果是异步数据，先去查找option中是否有选中项，用于改变选中项的选中状态及返回option中的该选中项
       * 若存在，则直接返回查找到的值。若不在，则直接返回传入的选中项
       * 传入的选中项和查找返回选中项都必须是object，因此lvAsync和lvValueKey不可同时使用
       */
      let temp;
      if (this.lvAsync) {
        temp = this._selectValue(item, flatData) ? this._selectValue(item, flatData) : item;
      } else {
        temp = this._selectValue(item, flatData);
      }

      if (temp) {
        this.value.push(temp);
      }
    });
    if (!this.multiple) {
      this.value = this.value.pop();
    } else {
      this.value = this.value.length ? this.value : undefined;
    }

    // 更新状态
    this.setNodesActive(); // 重新更新data的active状态
    this.setRefresh();
  }

  async preScrollByVirtualScroll(key: number, baseComponent?: SubSelectComponent, base?: HTMLElement): Promise<void> {
    return new Promise((resolve): void => {
      if (!this.lvVirtualScroll || ![UP_ARROW, DOWN_ARROW].includes(key)) {
        resolve();
      }
      const viewPort = (baseComponent?.parent || this)._scrollViewport;
      const scroll = viewPort.scrollable.elementScrolled().subscribe((): void => {
        setTimeout((): void => {
          ((baseComponent?.parent || this) as any)._cdr.detectChanges();
          resolve();
          scroll.unsubscribe();
        });
      });
      switch (true) {
        case this.isVirtualScrollTop(baseComponent, viewPort, base) && key === UP_ARROW:
          viewPort.scrollToIndex(viewPort.getDataLength() - 1);
          break;

        case this.isVirtualScrollRenderedContentTop(baseComponent, base) && key === UP_ARROW:
          viewPort.scrollToOffset(viewPort.measureScrollOffset() - viewPort.measureRenderedContentSize());
          break;

        case this.isVirtualScrollRenderedContentBottom(baseComponent, viewPort, base) && key === DOWN_ARROW:
          viewPort.scrollToOffset(viewPort.measureScrollOffset() + base.offsetTop + base.clientHeight);
          break;

        case this.isVirtualScrollBottom(baseComponent, viewPort, base) && key === DOWN_ARROW:
          viewPort.scrollToIndex(0);
          break;

        default:
          scroll.unsubscribe();
          resolve();
      }
    });
  }

  async onKeyDown(e: KeyboardEvent): Promise<void> {
    const { keyCode } = e;
    const keyCodeArr = [UP_ARROW, DOWN_ARROW, ENTER];
    if (!keyCodeArr.includes(keyCode)) {
      return;
    }
    e.preventDefault();
    const { baseComponent, base } = this.getBase();
    await this.preScrollByVirtualScroll(keyCode, baseComponent, base);
    let next: HTMLElement;
    const map = {
      [UP_ARROW]: (): void => {
        this.removeBaseHover(base);
        next = (
          this.getNextComponent(baseComponent, true) ||
          this.excludeDisabledItem((baseComponent?.parent || this).subSelect).last ||
          this.excludeDisabledItem(this.subSelect).last
        )?.elementRef.nativeElement;
      },
      [DOWN_ARROW]: (): void => {
        this.removeBaseHover(base);
        next = (
          this.getNextComponent(baseComponent) ||
          this.excludeDisabledItem((baseComponent?.parent || this).subSelect).first ||
          this.excludeDisabledItem(this.subSelect).first
        )?.elementRef.nativeElement;
      },
    };
    if (map[keyCode]) {
      map[keyCode]();
      this.moveToNext(next);
      this.listenMouseEnter(this.excludeDisabledItem((baseComponent?.parent || this).subSelect), next);
    } else {
      base?.querySelector('lv-option').dispatchEvent(new Event('click'));
    }
  }

  // 打开下拉panel overlay
  _openPanelOverlay(): void {
    this._selectWidth = this._activeElement.clientWidth;
    if (this._existOverlayRef()) {
      return;
    }
    const config = this._getPanelConfig();
    const extendConfig = {
      clickToCloseStrategy: { exclusions: '.lv-subselect-panel-content' },
      beforeOpen: this._beforeOpen,
      afterOpen: this._afterOpen,
      beforeClose: this._beforeClose,
      afterClose: this._afterClose,
    };

    // 处理没点按钮 下拉消失选中状态不对应问题
    const _portal = new TemplatePortal(this._selectionPanelTpl, this.viewContainerRef);
    this.overlayRef = this.overlayService.open(_portal, config, extendConfig);
    this.setRefresh();
  }

  _beforeOpen = (): Promise<any> =>
    new Promise((resolve): void => {
      this.beforeOpen(resolve);
    });

  _afterOpen = (overlayRef: any, res: any): void => {
    this.overlayRef = overlayRef;
    this.afterOpen(overlayRef, res);
    this.lvOpenChange.emit(this.selectOpen);
  };

  _beforeClose = (self, portal, event): any => {
    // 点击的path包含subSelect不管，其他都关
    let result;
    if (!event || !event.target) {
      result = true;
    } else {
      const path = this.selectService.getTargetPath(event.target);
      const subTargetName = 'LV-SUBSELECT';
      result = !_some(path, (item): boolean => item.tagName === subTargetName);
    }

    return new Promise<void>((resolve): void => {
      if (!result) {
        return;
      }
      this.beforeClose();
      const closesubscribe = this._openState$.subscribe((open): void => {
        if (!open) {
          closesubscribe.unsubscribe();
          resolve();
        }
      });
    });
  };

  _afterClose = (): void => {
    this.selectOpen = false;
    this._opened = false;
    this.clearAddStatus();
    this.lvOpenChange.emit(this.selectOpen);
    this._onTouch();
    this.setRefresh();
  };

  private _onTouch: any = (): void => {};

  private _onChange: any = (): void => {};

  private hasChildren(): any {
    return this.lvOptions?.some((item): number => item.children?.length);
  }

  private getVirtualScrollY(): any {
    return `${Math.min(this.lvVirtualItemSize * this.renderOptions.length, parseFloat(this.scrollY))}px`;
  }

  private clearAddStatus(): void {
    this.adding = false;
    this.addLoading = false;
    this.addingInputValue = '';
    this._cdr.markForCheck();
    this._overlayUpdatePostion();
  }

  private getBase(): any {
    this.selectedSubSelect = null;
    this.hoveredSubSelect = null;
    this.setSubSelectParent(Array.from(this.excludeDisabledItem(this.subSelect)), null);
    const baseComponent = this.hoveredSubSelect || this.selectedSubSelect;
    const base = baseComponent?.elementRef.nativeElement as HTMLElement;
    return { baseComponent, base };
  }

  private execAddCallback(addCallBack: (inputValue: string) => void | Promise<void> | Observable<void>): void {
    this.addLoading = true;
    const cb = addCallBack?.(this.addingInputValue);
    doFnResult(cb, (success: boolean): void => {
      if (!success) {
        this.addLoading = false;
        this._cdr.markForCheck();
        return;
      }
      this.clearAddStatus();
    });
  }

  private initAddOptionButtons(): void {
    if (!this.lvShowAddOption) {
      return;
    }
    const cancelButton = {
      label: 'cancel',
      onClick: (): void => {
        this.clearAddStatus();
      },
    };
    const addButton = {
      label: 'add',
      onClick: (): void => {
        this.execAddCallback(this.lvOnAddOption);
      },
    };
    const addAndSelectButton = {
      label: 'addAndSelect',
      onClick: (): void => {
        this.execAddCallback(this.lvOnAddAndSelectOption);
      },
    };
    if (typeof this.lvShowAddOption === 'boolean') {
      this.addOptionButtons = [cancelButton, addButton, addAndSelectButton];
    } else {
      this.addOptionButtons = [
        cancelButton,
        ...(this.lvShowAddOption.addBtn ? [addButton] : []),
        ...(this.lvShowAddOption.addAndSelectBtn ? [addAndSelectButton] : []),
      ];
    }
  }
}
